﻿using System;
using System.IO;
using System.Net;
using System.Threading;

namespace mVu
{
  public delegate void DownloadProgressHandler(DownloadInfo info);

  class Download
  {
    public ManualResetEvent AllDone = new ManualResetEvent(false);
    const int BufferSize = 4096;

    public void DownloadFile(string url, string filename, DownloadItem di, DownloadProgressHandler progressComboBox)
    {
      try
      {
        // Ensure flag set correctly.			
        AllDone.Reset();

        // Get the URI from the command line.
        var httpSite = new Uri(url);

        long startPoint = 0;

        if (File.Exists(filename))
        {
          startPoint = new FileInfo(filename).Length;
        }

        // Create the request object.
        var request = (HttpWebRequest)WebRequest.Create(httpSite);
        request.UserAgent = "Mozilla/4.0";
        request.Proxy = null;
        request.Timeout = 500000;
        request.ReadWriteTimeout = 500000;
        ServicePointManager.DefaultConnectionLimit = 65000;

        // Create the state object.
        var info = new DownloadInfo
                     {
                       Request = request,
                       Filename = filename,
                       Item = di
                     };
        info.ProgressCallback += progressComboBox;

        if (startPoint > 0)
        {
          request.AddRange(Convert.ToInt32(startPoint));
          info.BytesProcessed = Convert.ToInt32(startPoint);
          info.StartPoint = Convert.ToInt32(startPoint);
        }

        // Issue the async request.
        var r = request.BeginGetResponse(new AsyncCallback(ResponseCallback), info);

        // Wait until the ManualResetEvent is set so that the application
        // does not exit until after the callback is called.
        AllDone.WaitOne();
      }
      catch (Exception ex)
      {
        Utilities.HandleError(ex);
      }
    }

    private void ResponseCallback(IAsyncResult ar)
    {
      try
      {
        // Get the DownloadInfo object from the async result were
        // we're storing all of the temporary data and the download
        // buffer.
        var info = (DownloadInfo)ar.AsyncState;

        // Get the WebRequest from RequestState.
        var request = info.Request;

        // Call EndGetResponse, which produces the WebResponse object
        // that came from the request issued above.
        var response = request.EndGetResponse(ar);
        info.DataLength = Convert.ToInt32(response.ContentLength) + info.BytesProcessed;

        //  Start reading data from the response stream.
        var responseStream = response.GetResponseStream();
        info.ResponseStream = responseStream;

        var fileStream = info.BytesProcessed > 0 ? new FileStream(info.Filename, FileMode.Append, FileAccess.Write, FileShare.Read, BufferSize, true) : new FileStream(info.Filename, FileMode.Create, FileAccess.Write, FileShare.Read, BufferSize, true);
        info.FileStream = fileStream;

        //  Pass do.BufferRead to BeginRead.
        var iarRead = responseStream.BeginRead(info.BufferRead,
                                               0,
                                               BufferSize,
                                               new AsyncCallback(ReadCallBack),
                                               info);
      }
      catch (Exception ex)
      {
        Utilities.HandleError(ex);
      }
    }

    private void ReadCallBack(IAsyncResult asyncResult)
    {
      try
      {
        // Get the DownloadInfo object from AsyncResult.
        var info = (DownloadInfo)asyncResult.AsyncState;

        // Retrieve the ResponseStream that was set in RespCallback.
        var responseStream = info.ResponseStream;
        var fileStream = info.FileStream;

        // Read info.BufferRead to verify that it contains data.
        var bytesRead = responseStream.EndRead(asyncResult);
        if (bytesRead > 0 && info.Item.IsDownloading && !info.Item.IsPaused)
        {
          fileStream.Write(info.BufferRead, 0, bytesRead);
          fileStream.Flush();

          info.BytesProcessed += bytesRead;

          if (info.ProgressCallback != null)
          {
            info.ProgressCallback(info);
          }
          
          // Continue reading data until responseStream.EndRead returns –1.
          var ar = responseStream.BeginRead(
            info.BufferRead, 0, BufferSize,
            new AsyncCallback(ReadCallBack), info);
        }
        else
        {
          fileStream.Close();
          responseStream.Close();
          AllDone.Set();
        }
      }
      catch (Exception ex)
      {
        Utilities.HandleError(ex);
      }

      return;
    }
  }
}
